APPENDIX B
Next Section Previous Section Table of Contents Index Errata

Extending LScript Shared Libraries: User-Defined Object Agents

(Current Interface Version: 1.1)

By utilizing the shared library mechanism, LScript allows you to write shared libraries can be used to implement user-defined Object Agents. What this means is that new Object Agents (technically, these are referred to as Class Definitions) can be written within shared libraries, and instances of these Object Agents (Class Definitions) can be created in an LScript by a programmer utilizing the following declaration:

          use "<filename>.dll" as class <ClassName>;
The protocol for creating your own Object Agents is much more complex than that of simply creating a LScript-callable shared-library function. Because we have taken pains to emulate C++ class design, Object Agent shared libraries will contain "methods" (functions) that will only be accessible by the LScript engine (such as destructors), along with the normal "public" items that an Object Agent makes available to LScripts, such as data members and exported methods.

To better illustrate the requirements of building a user-defined Object Agent, we will construct a questionably-useful Object Agent called BobObj. BobObj will have only one "public" method, stradd(), which will concatenate two character strings together, returning the new string as its only return value.

As with DLL functions, topics covered in this section are of a programmatic content and are non-trivial in nature. They are intended for more advanced developers.

Activating Object Agent Access

In order to link Object Agents into your LScripts, you must make LScript aware of their location. This is accomplished through the use of the 'LibraryPath' LScript global variable. Global variables can be placed where they can be viewed by all LScripts regardless of their architecture, or they can be situated such that they are only visible globally to other LScripts of the same architecture.

The location where these global values reside varies across LightWave 3D platforms. Users of LScript v1.3 or higher can utilize the globalstore() and globalrecall() functions to place values into the LScript-global areas. If, however, you are using versions of LScript prior to this, you must enter values manually.

Setting Global Variables Under Intel (x86)

The Intel version of LScript utlizes the 95/NT System Registry to store its settings. The Registry keys for these settings starts at:

     HKEY_CURRENT_USER\Software\NewTek\LScript
Under this key are other keys that are related directly to the specific LightWave plug-in architectures that LScript supports--LScript/IA, LScript/DM, LScript (Modeler), etc. It is within these individual architecture keys that the store() and recall() script functions place/retreive their values.

To make the 'LibraryPath' setting available to all LScript architectures (recommended), you would create a new string value in the key mentioned above. This new string value will have the identifier LibraryPath, and its value will be the path to the directory wherein ObjectAgent libraries and script insert files can be found.

If you wish or need to store such files in more than one location, you can place up to 10 directory paths into the string value for LScript to search through. Each path must be separated from the others by a semi-colon (much the same as the MS-DOS PATH value).

        Name                Data

        (Default)           (value not set)
        LibraryPath         "f:\lw\dev\lscript\dll;f:\oa;c:\temp"

Setting Global Variables Under DEC Alpha

Due to the age of the compiler for the DEC Alpha LightWave platforms, Registry access is not afforded to programs. Therefore, at the current time, you cannot set values globally for all LScript architectures. You can however set them individually.

LScript on the DEC Alpha creates configuration files for each individual architecture. These configuration files are simple ASCII text, and can be edited by any text editor. Typically, these configuration files reside in the LightWave program directory (NewTek\Programs). The format of these configuration files is identical to the Windows INI file format. When setting the 'LibraryPath' value for an architecture, you will want to place it into the [LScript] section of the configuration file.

        [LScript]
        ScriptPath=c:\bob\lscript\examples
        LibraryPath=c:\bob\lscript\ObjectAgent

Setting Globals under Sun and SGI

Similar to the DEC Alpha, UNIX versions of LScript create hidden configuration files for each architecture in the users $HOME directory. UNIX LScript configuration files adhere to the Windows INI format, so you can simply follow the directions above for DEC Alpha.

Setting Globals under Macintosh PPC

LScript for the Macintosh stores its individual configuration files in the System Preferences Folder. Configuration file formats are Windows INI-compatible, so once you locate the specific architecture's file, follow the directions above.

Extensions to 'lscript.h'

To facilitate user-defined Object Agents, additional structures and constants have been included in the lscript.h header file.

A new structure is defined that houses an instance of the LSFunc structure discussed in Appendix A on DLL functions, along with several other data pointers:

         typedef struct _lsdll
         {
             const char *ClassName;
             const char *InstanceName;

             LS_VAR  *instance[MAXINSTANCE];
             LSFunc  *func;
         } LSDLL;
The 'ClassName' member points to a character string that contains the Object Agent 'class' name, designated in the LScript.

'InstanceName' points to a character string that contains the variable name that contains the current instance of the Object Agent.

The 'instance' array is a series of MAXINSTANCE arrays of Object Agent instance data. The current instance can always access its own instance data, if there is any, through instance[0]. Although they are currently unused, the remaining elements are reserved for future instance data that may be available as a result of object inheritance. Instance data is discussed later in this section.

A new prototype has also been is defined for use in creating your "public" Object Agent methods. This prototype will be used by the LScript engine to invoke your methods:

         typedef LS_VAR * (*ObjectAgentMethod)(int *,LS_VAR *,LSDLL *);

Private Class Methods

Each Object Agent shared library is required to make available to LScript four methods that represent the basis of the new Object Agent's management functions. Each of these four required methods accepts a differing number of parameters. Two additional methods are optional in the interface, with the need for their presence depending entirely upon the behavior of one of the four required methods.

The four required methods are:

         void constructor(count,variables,classinfo)

                'count' is an integer value representing the number of
                parameters that were passed to this constructor

                'variables' is a pointer to an LS_VAR array containing
                the values in the parameters that were passed to the
                constructor.  it will be NULL (and 'count' will be 0)
                if no parameters were passed

                'classinfo' is a pointer to an instance of an LSDLL structure
                (discussed earlier) that contains information about the
                class and instance names, pointers to internal LScript
                functions, and pointers to instance data

         void destructor(count,variables,classinfo)

                parameters are identical to those of the constructor, however
                because destructors cannot be invoked directly, 'count' will
                always be 0 and 'variables' will always be NULL
                
         boolean ownmethod(methodname)

                this interface method is used by LScript to see if an Object
                Agent instance owns the specified method.  'methodname' is a
                character string that names the method in question.
                ownmethod() should use this character string to compare to
                its known "public" methods, returning 'true' if one of its
                methods matches this name, or 'false' if the specified method
                is not available

         boolean owndata(dataname)

                this private method is similar to ownmethod(), but is intended
                to be applied to the "public" data members that the Object
                Agent makes available.  'dataname' is a character string that
                contains the name of the data member in question
In addition, three more methods can exist in the interface. Two are used to manage access to "public" data members of the Object Agent Class, while the third locks the Object Agent into a specific revision of the Object Agent Interface (defined by the OAMAJOR and OAMINOR declarators in the lscript.h header file):

         LS_VAR *returndata(dataname,classinfo)

                returndata() returns the value contained by the Object Agent
                data member named by 'dataname->extra', a void * that
                should be cast to a char *.  returndata() will never
                be called for a data member unless owndata() has previously
                indicated that that data member is owned by this Object Agent

                the 'dataname->data.number' member should be cast to an
                integer value if the referenced data member is an array.
                'dataname->data.number' is the index value used within the
                script, and should be converted to a zero-based offset.

                PLEASE NOTE that this value should only be cast if it is
                needed as an index value.  Casting this value when it has
                not been explicitly set by the scripting engine can cause
                certain operating systems (notably, the DEC Alpha) to
                generate a Floating Point exception error.

         assigndata(dataname,data,classinfo)

                LScript will invoke this Object Agent method when a script
                needs to assign new data to a data member.  LScript will
                handle, internally, any of the possible means of assigning
                new values to existing data members.  this method will be
                passed the result, and be expected to update the appropriate
                data member.

                as with returndata(), 'dataname' is a pointer to an LS_VAR
                structure that houses both the data member identifier
                in the 'extra' area, and, if applicable, the requested
                index offset in the 'data.number' area if the data member
                can be indexed.

                'data' is another LS_VAR pointer that contains the new value

                'classinfo' is an instance of LSDLL.

         version(major,minor)

                'major' and 'minor' are integer pointers that will receive
                the required major and minor revision values that indicate
                the level of the Interface that this Object Agent requires to
                function.  If the returned values do not match the revision
                of the current Object Agent Interface specification supported
                by the plug-in, then the library will not be loaded and
                unresolved function references will likely result.
A special behavior of the interface involves the existence of "public" data members in your Object Agent class. If your particular Object Agent makes available no "public" data members (i.e., owndata() unconditionally returns a 'false' value), then the returndata() and assigndata() methods become optional, and need not be present in your interface. The reason for this is that these two methods will never be invoked by the LScript engine unless owndata() returns 'true'.

We will see examples of the usage of each of these methods later in this section when we write the BobObj Object Agent.

Handling Multiple Instances

One optional private method that can exist in an Object Agent shared library is designed to handle creation of Object Agent instance data, and it is called instdata(). First, however, we should examine just what is meant by "instance data" and why we need it.

"Instance data" is essentially nothing more than a copy--or instance--of specific data values that an object must have in order to function. A good example of this need of instance data would be in a file-handling Object Agent. In our fictitious file-handling Object Agent, there will be methods defined that perform some type of specific functioning on the Object Agent data. Without the ability to handle instance data, you would have to maintain (and declare) a different Object Agent DLL for each FILE you wanted to handle as an object. Why? Because each DLL would have to have its own variables (declared at compile time) that it used to maintain items such as file name, FILE pointer, etc., but would only have one location where this information could be stored. If you needed to handle a new file using a single DLL, you would have to re-initialize an existing Object Agent with the new file information, or create a new instance of a declared Object Agent--both actions would have the net effect of destroying any previous data that was being maintained for that instance because it would have to be overwritten (remember, there is only ONE copy of the DLL loaded into memory, not one copy for each instance you create).

Another solution might be to have the individual Object Agent shared library provide its own mechanism for maintaining separate instance data. However, this means that Object Agent shared library writers would have to devise their own storage mechanisms instead of using the same established standard, and additional information might have to be provided to the shared library function so that they could distinguish among their potentially-many instances.

LScript employs the instdata() function for allowing an Object Agent shared library to create a new copy of the instance data that the Object Agent will need to function properly. The instance data created by instdata() is not required to be populated at the same time that it is created, because instdata(), if it exists, is always called before the Object Agent's constructor (which is the most appropriate place to initialize instance data values). If an Object Agent shared library does not contain an instdata() method, then LScript assumes that the Object Agent will have no instance data, and that particular value in the LSDLL structure (instance[0]) will be set to NULL.

If an Object Agent needs to maintain instance data, then the instdata() function should be present and designed to allocate the necessary storage locations. Each element of instance data is stored in a LS_VAR data wrapper, and passed back to LScript as a LS_VAR pointer. If you have more than one element, then you would need to allocate a linear array of LS_VAR data wrappers, and return a pointer to the first element to LScript after completing your processing. This same pointer returned by instdata() will be placed into the first element of the LSDLL structures instance array (instance[0]), which is passed to other Object Agent methods.

In the sample Object Agent we will create, we have no real need for maintaining instance data, but we do so for the sake of providing an example.

The BobObj Shell

First, we will lock the Object Agent into version 1.1 of the Object Agent Interface by providing a version() method:

    void version(int *maj,int *min)
    {
        *maj = 1; // need revision 1.1
        *min = 1; // (this is the interface version, NOT the plug-in version)
    }
Because we will maintain instance data--albeit, dummy data--in the BobObj Object Agent, we will begin with that method. Keep in mind that this method is optional.

In BobObj, we will maintain three instance values, named "tom," "dick" and "harry." These instance values are all considered "public", and all will be accessible by the host LScript. We will not actually have to name them thus internally, but rather we need to be aware that this will be how the script may reference them. Because we do not initialize the instance data in the instdata() method, it becomes quite simple:

    LS_VAR * instdata(LSFunc *func)
    {
        LS_VAR  *mydata;

        // we have three data members, all "public"

        mydata = (LS_VAR *)(*func->malloc)(3 * sizeof(LS_VAR));

        return(mydata);
    }
Here are some important things to be aware of regarding this method:

    1.  Do not use "static" variables to house your instance data.  Static
        variables are single values that are shared between instances.  While
        this may be your intention, more often than not you will want each
        instance to have its own unique set of data values from which to
        operate.  For this reason, they must be allocated from the run-time
        heap.

    2.  You can structure instance data any way you wish, but no count is
        maintained by LScript of the number of entries in your instance data.
        It is assumed that the contents of the instance array are all known to
        the Object Agent, so access to the data in the array should NEVER
        exceed its boundaries.

    3.  Be sure to use the LScript-provided resource-management functions so
        that you don't introduce memory leaks to LightWave 3D.  LScript tracks
        all allocations/releases of internal memory, and releases any
        remaining memory allocations when it terminates.  If you use the
        standard library malloc(), LScript won't know about it and leaks could
        occur if you forget to explicitly free() it.
If present, this will be the first method called by the LScript engine when a new instance is created in the script.

The next method we will complete is the Object Agent's constructor. The constructor is a required method, and it will be invoked whenever the script creates a new instance of the Object Agent. The constructor will always be called immediately following the invocation of instdata(), or it will be the first method invoked if instdata() does not exist.

In the BobObj constructor, we will initialize the instance data members that were created in instdata(). Constructors can be passed parameters by the script, and you will typically want to initialize your instance data using values that are derived from these parameters. We do not use constructor parameters in the BobObj Object Agent.

    void constructor(int count,LS_VAR *parameters,LSDLL *lsdll)
    {
        LS_VAR *inst;

        if(count != 0)      // error! we don't accept parameters
        {
            // (In C++: no class method matching this signature)

            (*lsdll->func->error)(...);
            return;
        }

        if((inst = lsdll->instance[0]) != NULL)  // just be sure...
        {
            inst[0].data.number = 145.567;

            inst[1].data.string = "Dick";     // constant data member

            inst[2].data.vector[0] = 10.0;
            inst[2].data.vector[1] = 20.0;
            inst[2].data.vector[2] = 30.0;
        }
    }
As mentioned earlier, an Object Agent's constructor is invoked at the time the instance is created in an LScript. Instances are created by the script programmer when the class name of the Object Agent is used as a function call. For instance, when we create instances of the BobObj Object Agent, we invoke the class constructor like so:

      use "bobobj.dll" as class BobObj;
      ...
      main
      {
          ...
          bob1 = BobObj();  // create a BobObj instance
          ...
Had we been required to pass arguments to the Object Agent constructor, we would have placed them within the parentheses:

      bob1 = BobObj(1,"Hello");  // create an instance using parameters
Some important things to note about an Object Agent's constructor method:

    1.  In the constructor, you should establish the appropriate data values
        in your instance data for this instance of the class.  These values
        will usually be derived in some way from the parameters that are
        passed to this method.  For example, were this some type of file-
        handling class, we would expect one of the parameters to indicate
        a unique filename the Object Agent would be expected to manage.

    2.  You can simulate method overloading in any Object Agent method simply
        by being flexible about the count and type of arguments provided to
        the method.  You can then look for the patterns that match what those
        overloaded methods your Object Agent supports.  Anything else would
        be a run-time error.

    3.  The 'InstanceName' attribute of the LSDLL structure has not yet been
        established when the Object Agent's constructor is invoked.  The reason
        for this is that the instance being created has not yet been assigned
        to a variable at the point in time when the constructor is called.
        DO NOT USE IT!
All well-written class definitions should include a destructor method. The destructor method is used to "clean up" the instance that is going away, or "out of scope." The bulk of this clean up is usually the graceful shutdown of any instance data members that require it, and freeing of the instance's resources (memory, files, etc.). It is within the destructor that we also release any resources allocated by the instdata() method.

Because the BobObj Object Agent we are creating manages no complex instance data, it's destructor is quite simple:

    void destructor(int count,LS_VAR *parameters,LSDLL *lsdll)
    {
        if(lsdll->instance[0])  // free instdata()-allocated resources
            (*lsdll->func->free)(lsdll->instance[0]);
    }
Of course, had we allocated additional resources within each instance data element, we would traverse each element in the destructor, invoking the appropriate LScript function to release it (free(), fclose(), etc.) before releasing the instance data array itself.

The next two required Object Agent methods are ownmethod() and owndata(). These two methods are quite straight-forward, in that they simply return a logical 'true' or 'false' to indicate the Object Agent's ability to service specific methods or data members. Our BobObj Object Agent sports one public method, "stradd()", and three public data members, "tom," "dick" and "harry."

    int ownmethod(const char *method)    // do we own a specific method?
    {
        if((method[0] == 's') &&
           (strcmp(method,"stradd") == 0)) return(TRUE);

        return(FALSE);      // we don't own this method...
    }

    int owndata(const char *data)        // do we own a specific data member?
    {
        if((data[0] == 't') &&
           (strcmp(data,"tom") == 0))   return(TRUE);
        else if((data[0] == 'd') &&
           (strcmp(data,"dick") == 0))  return(TRUE);
        else if((data[0] == 'h') &&
           (strcmp(data,"harry") == 0)) return(TRUE);

        return(FALSE);                  // we don't own this data member...
    }
Notice in these two methods the lack of any error handling. When a method or data member is not found, neither method generates an error message of any kind. This is quite deliberate in design. We are allowing the LScript engine to decide how to handle this situation.

It is a possibility that in future revisions of this interface, the LScript engine will be able to support Object Agent inheritance, where one Object Agent can inherit the methods and data members of another, creating a new Object Agent all within the operation of the LScript. The reason we do not generate an error of some kind when we cannot locate a method or object method is that a negative return value from either of these methods could cause the LScript engine to query our parent(s) (or superclasses). In the current revision, however, the LScript engine simply generates an error condition on the Object Agent's behalf.

Probably the two methods with the greatest potential for size are the final required methods, returndata() and assigndata(). These methods are responsible for handling access to the Object Agent's public data members.

Returndata() is responsible for returning the value contained in a requested Object Agent data member. This method will not be called by the LScript engine unless the owndata() method has first been used to ensure that the data member to be accessed belongs to the Object Agent. Nevertheless, returndata() should always be prepared to handle situations where the provided data member identifier is not appropriate for the Object Agent. A return value of NULL indicates this error condition.

Here is the BobObj-version of returndata():

    LS_VAR * returndata(LS_VAR *datamember,LSDLL *lsdll)
    {
        static LS_VAR lsvar;      // assigned a different value on each call
        static LS_VAR *inst;
        static char   *identifier;
        static int    index;
    
        if(!lsdll->instance[0])    // no instance data, that's a problem
            return(NULL);
    
        inst = lsdll->instance[0]; // use some short-hand for easier reading

        identifier = (char *)datamember->extra;
        index = (int)datamember->data.number;
    
        if((identifier[0] == 't') && (strcmp(identifier,"tom") == 0))
        {
            lsvar.type = LSNUMBER;
            lsvar.data.number = inst[0].data.number;
        }
        else if((identifier[0] == 'd') && (strcmp(identifier,"dick") == 0))
        {
            lsvar.type = LSSTRING;
            lsvar.data.string = inst[1].data.string;
        }
        else if((identifier[0] == 'h') && (strcmp(identifier,"harry") == 0))
        {
            lsvar.type = LSVECTOR;
            lsvar.data.vector[0] = inst[2].data.vector[0];
            lsvar.data.vector[1] = inst[2].data.vector[1];
            lsvar.data.vector[2] = inst[2].data.vector[2];
        }
        else
        {
            // owndata() said we owned it, but we really don't (this should
            // never happen, but...)

            sprintf(errormsg,"bad data member name %s::%s.%s",
                    lsdll->ClassName, lsdll->InstanceName, identifier);
            (*lsdll->func->error)(errormsg);    // halt the script

            return(NULL);
        }
    
        return(&lsvar);
    }
Assigndata() performs a function very similar to returndata(), however it updates data member values instead of returning them. It also performs a number of sanity checks on the value being used for the update to ensure data member integrity:

    void assigndata(LS_VAR *datamember,LS_VAR *value,LSDLL *lsdll)
    {
        static LS_VAR *inst;
        static int i1,i2;
        static char    *identifier;
        static int  index;
    
        if((inst = lsdll->instance[0]) == NULL)    // shorthand
            return;
    
        identifier = (char *)datamember->extra;
        index = (int)datamember->data.number;

        if((identifier[0] == 't') && (strcmp(identifier,"tom") == 0))
        {
            if(value->type != LSINTEGER && value->type != LSNUMBER)
            {
                sprintf(errormsg,
                        "invalid data type in data member assignment %s::%s.%s",
                        lsdll->ClassName, lsdll->InstanceName, identifier);
                (*lsdll->func->error)(errormsg);
                return;
            }
    
            inst[0].data.number = value->data.number;
        }
        else if((identifier[0] == 'd') && (strcmp(identifier,"dick") == 0))
        {
            // since we consider this to be a "constant" data member, any
            // assignment type would be illegal
    
            sprintf(errormsg,
                    "illegal assignment to constant data member %s::%s.%s",
                    lsdll->ClassName, lsdll->InstanceName, identifier);
            (*lsdll->func->error)(errormsg);
        }
        else if((identifier[0] == 'h') && (strcmp(identifier,"harry") == 0))
        {
            if(value->type != LSVECTOR &&
               value->type != LSINTEGER &&
               value->type != LSNUMBER)
            {
                sprintf(errormsg,
                        "invalid data type in data member assignment %s::%s.%s",
                        lsdll->ClassName, lsdll->InstanceName, identifier);
                (*lsdll->func->error)(errormsg);
                return;
            }

            // we allow integer/number values to be used, as well as
            // vectors

            if(value->type == LSVECTOR)
            {
                inst[2].data.vector[0] = value->data.vector[0];
                inst[2].data.vector[1] = value->data.vector[1];
                inst[2].data.vector[2] = value->data.vector[2];
            }
            else
                inst[2].data.vector[0] =
                inst[2].data.vector[1] =
                inst[2].data.vector[2] = value->data.number;
        }
        else
        {
            sprintf(errormsg,"bad data member name %s::%s.%s",
                    lsdll->ClassName, lsdll->InstanceName, identifier);
            (*lsdll->func->error)(errormsg);
        }
    }
Finally, to complete our BobObj Object Agent implementation, we need to create our single public method, stradd(). Notice that the public method stradd() adheres to the function prototype set forth in the 'lscript.h' header file for Object Agent "public" methods:

    LS_VAR * stradd(int *count,LS_VAR *vars,LSDLL *lsdll)
    {
        static  LS_VAR var;
        static  char buf[512];
        static  char num[100];
        static  LS_VAR *inst;

        if(*count != 2)   // check our "signature"
            return(NULL);

        *count = 0;   // prepare to tell the engine how many return values

        if(vars[0].type == LSSTRING && vars[1].type == LSSTRING)
        {
            inst = lsdll->instance[0];

            sprintf(num," (( <%g,%g,%g> ))",  // this value will be appended
                      inst[2].data.vector[0], // to each string as a visual
                      inst[2].data.vector[1], // authentication of the instance
                      inst[2].data.vector[2]);

            var.type = LSSTRING;
            if(strlen(vars[0].data.string) >= 512)
                strncpy(buf,vars[0].data.string,500);
            else
                strcpy(buf,vars[0].data.string);

            if((strlen(buf) + strlen(vars[1].data.string)) < 500)
                strcat(buf,vars[1].data.string);

            strcat(buf,num);
            var.data.string = buf;

            *count = 1;  // tell engine that there is one element returned
            return(&var);
        }
        else
            return(NULL);
    }
Public Object Agent methods have some operational points that need to be mentioned:

    1.  Notice that the first parameter to stradd(), used to indicate the
        number of script parameters that are contained in the 'vars' list,
        is a pointer instead of a value.  The reason for this is that this
        location is used to indicate to the LScript engine the number of
        elements that are being returned from the method.  In the case of
        stradd(), only one element is returned, so this value is set to 1
        before returning (and initialized to 0 in case of error).

    2.  LScript is not aware of the arrangements you may have made within your
        code for the "containers" used to return values.  You may have
        elected to use static values, or you may have allocated them from
        the heap.  Because of this, the LScript engine cannot make assumptions
        concerning the disposition of your containers when it has finished
        processing their values (i.e., should it free them from the heap or
        simply ignore them?).  For this reason, the method (or Object Agent)
        is solely responsible for allocating these items, and releasing them
        when they are no longer needed.  Suggested practices for creating
        these resources might be to use a static array of LS_VARs, or to
        allocate the additional values needed in the Object Agent's instance
        data (instdata()).  However, the use of static storage is the
        recommended method of returning values to the engine; allocating
        memory consumes critical resources and CPU time.

        If using static storage is not a viable option within your method, and
        you return the same number of items each time, a somewhat more elegant
        solution would be to allocate a static pointer in your method, and
        then allocate the needed elements using the provided LScript memory
        allocation function malloc() the first time the method is invoked.  In
        this way, the memory would not be allocated until it is really needed
        (the method may not even be invoked throughout the entire script), and
        the Object Agent will not have to be concerned about not being able to
        release the memory because LScript will catch these unreleased
        resources when it terminates the script.

            stradd(...)
            {
                static LS_VAR *myvars = NULL;
                ...

                if(myvars == NULL)
                    myvars = (LS_VAR *)(*lsdll->func->malloc)
                                                      (sizeof(LS_VAR) * 5);

Creating The Object Agent Shared Library

Object Agent shared libraries are compiled in the same fashion as regular LScript-linkable DLLs. In fact, you could make a copy of the makefile provided in the previous section, and use it directly after changing the file names embedded within.

Once you have compiled your new Object Agent DLL, it must be placed into one of the locations in which the LScript engine will look when trying to resolve references. These locations were outlined in the previous section.

Exercising BobObj

Our final action with the BobObj Object Agent is to actually test it inside an LScript. Here, then, is a complete LScript written to exercise the features of the Object Agent mechanism, as well as the methods and data members that are publicly available from BobObj:

    use "bobobj.dll" as class BobObj;

    bob0 = BobObj();   // a global instance

    main
    {
        bob1 = BobObj();        // two instances local to main()
        bob2 = BobObj();

        info(bob1);             // Object Agent instances can be passed
        info(bob2);             // as parameters

        info(bob1.stradd("Hello","There"));
        info(bob2.stradd("I'm","Bob"));

        info(bob1.tom);
        info(bob1.dick);
        info(bob2.harry);

        info("calling test!");
        test();

        bob1.tom += 15;
        info(bob1.tom);

        bob2.harry /= 10;
        info(bob2.harry);

        test();

        info(bob0.dick);
        info(bob0.tom++);   // post-increment
        info(bob0.tom);

        // bob1 and bob2 destructor invoked upon exit from main()

    }   // bob0 destructor invoked upon termination of script

    test
    {
        bob3 = BobObj();        // instance local to test()

        info(bob3.harry);

        // bob3 destructor is invoked upon exit from test()
    }
The output from this LScript shows that the many instances of our BobObj Object Agent are all alive and well and functioning properly:

    INFO: (ObjectAgent)BobObj::bob1
    INFO: (ObjectAgent)BobObj::bob2
    INFO: HelloThere (( <10,20,30> ))
    INFO: I'mBob (( <10,20,30> ))
    INFO: 145.567
    INFO: Dick
    INFO: <10,20,30>
    INFO: calling test!
    INFO: <10,20,30>
    INFO: 160.567
    INFO: <1,2,3>
    INFO: <10,20,30>
    INFO: Dick
    INFO: 145.567
    INFO: 146.567

Next Section Previous Section Table of Contents Index Errata
© 1996 Virtual Visions, Inc.
© 1997 NewTek, Inc.